package io.renren.utils.util.db.connection;

import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import io.renren.utils.util.SystemConfigUtil;

/**
 * This class is a Singleton that provides access to one or many connection
 * pools defined in a Property file. A client gets access to the single instance
 * through the static getInstance() method and can then check-out and check-in
 * connections from a pool. When the client shuts down it should call the
 * release() method to close all open connections and do other clean up.
 * 
 * @author $Author: clarkliu $
 */

public class DBConnectionManager {

	// ----------------------------------------------------- Static Variables
	public final static String PREFACE = "DBConnection";

	static Logger logger = Logger.getLogger(DBConnectionManager.class);

	private static DBConnectionManager instance; // The single instance

	private static ThreadLocal connection = new ThreadLocal();

	// ----------------------------------------------------- Instance Variables
	private Vector drivers = new Vector();

	private Hashtable pools = new Hashtable();

	// ----------------------------------------------------- Constructors
	/**
	 * A private constructor since this is a Singleton
	 */
	private DBConnectionManager() {
		init();
	}

	// ----------------------------------------------------- Public methods
	/**
	 * Returns the single instance, creating one if it's the first time this
	 * method is called.
	 * 
	 * @return DBConnectionManager The single instance.
	 */
	static synchronized public DBConnectionManager getInstance() {
		if (instance == null) {
			instance = new DBConnectionManager();
		}
		return instance;
	}

	/**
	 * Returns a connection to the named pool.
	 * 
	 * @param name
	 *            The pool name as defined in the properties file
	 * @param con
	 *            The Connection
	 */
	public void freeConnection(String name) {
		Connection conn = (Connection) connection.get();
		if (conn != null) {
			try {
				if (logger.isDebugEnabled()) {
					logger.debug("Release connction ("+name+") : "+conn);
				}
				//if (conn.getAutoCommit()) {
					connection.set(null);
					if (conn instanceof DBConnection) {
						DBConnectionPool pool = (DBConnectionPool) pools.get(name);
						pool.returnConnection((DBConnection) conn);
					} else {
						conn.close();
					}
				//}
			} catch (SQLException e) {
				logger.warn("JDBC CLOSE Connection EXCEPTION: message = " + e.getMessage());
			}

		}
	}

	/**
	 * Returns a connection to the named pool.
	 * 
	 * @param name
	 *            The pool name as defined in the properties file
	 * @param con
	 *            The Connection
	 */
	public void freeConnection(Connection conn) {
		if (conn != null) {
			try {
				if (logger.isDebugEnabled()) {
					logger.debug("Release connction : " + conn);
				}	
				//if (conn.getAutoCommit()) {
					connection.set(null);
					if (conn instanceof DBConnection) {					
						DBConnectionPool pool = (DBConnectionPool) pools.get(((DBConnection)conn).getPoolName());
						pool.returnConnection((DBConnection) conn);
					} else {
						conn.close();
					}
				//}
			} catch (SQLException e) {
				logger.warn("JDBC CLOSE Connection EXCEPTION: message = " + e.getMessage());
			}

		}
	}
	
	public void destroyConnection(String name, Connection con) {
		if (con instanceof DBConnection) {
			DBConnectionPool pool = (DBConnectionPool) pools.get(name);
			pool.removeConnection((DBConnection) con);
		}
		try {
			con.close();
		} catch (SQLException sqle) {
			logger.error("Cannot free connection " + name, sqle);
		}
	}

	/**
	 * Returns an open connection. If no one is available, and the max number of
	 * connections has not been reached, a new connection is created.
	 * 
	 * @param name
	 *            The pool name as defined in the properties file
	 * @return Connection The connection or null
	 */
	public synchronized Connection getConnection(String name) {
		Connection retVal = null;
		DBConnectionPool pool = (DBConnectionPool) pools.get(name);
		if (pool != null) {
			try {
				retVal = (Connection) connection.get();
				if (retVal == null || (retVal != null && retVal instanceof DBConnection && !((DBConnection)retVal).getPoolName().equals(name))) {
					if (logger.isDebugEnabled()) {
						logger.debug("Request connection from pool. " + name);
					}
					retVal = pool.getConnection();
					connection.set(retVal);
				} else {
					if (logger.isDebugEnabled()) {
						logger.debug("Request connection from ThreadLocal. " + name);
					}
				}
			} catch (SQLException sqle) {
				logger.warn("WARN: Returned Null Connection for " + name, sqle);
				return null;
			}
		} else {
			logger.warn("WARN: Returned Null Connection for " + name);
		}
		return retVal;
	}

	// ----------------------------------------------------- Other methods

	/**
	 * Loads properties and initializes the instance with its values.
	 */
	private void init() {
		loadDrivers();
		createPools();
	}

	/**
	 * Creates instances of DBConnectionPool based on the properties. A
	 * DBConnectionPool can be defined with the following properties:
	 * 
	 * <PRE>
	 * 
	 * &lt;poolname&gt;.url The JDBC URL for the database &lt;poolname&gt;.user
	 * A database user (optional) &lt;poolname&gt;.password A database user
	 * password (if user specified) &lt;poolname&gt;.maxconn The maximal number
	 * of connections (optional)
	 * 
	 * </PRE>
	 *  
	 */
	private void createPools() {
		String poolName = null;
		String aa = PREFACE + "." + "CONNECTION.NAME";
		String bb = SystemConfigUtil.getInstance().getStringProperty(aa);
		for (int i = 0; (poolName = SystemConfigUtil.getInstance().getStringProperty(
				PREFACE + "." + "CONNECTION[" + i + "].NAME", null)) != null; i++) {
			try {
				String url = SystemConfigUtil.getInstance().getStringProperty(PREFACE + ".CONNECTION[" + i + "].URL");
				String user = SystemConfigUtil.getInstance().getStringProperty(PREFACE + ".CONNECTION[" + i + "].USER");
				String password = SystemConfigUtil.getInstance().getStringProperty(
						PREFACE + ".CONNECTION[" + i + "].PASSWORD");
				int maxConnections = SystemConfigUtil.getInstance().getIntProperty(
						PREFACE + ".CONNECTION[" + i + "].MAXACTIVE", 20);
				DBConnectionPool pool = new DBConnectionPool(poolName, url, user, password, maxConnections);
				if (logger.isEnabledFor(Level.DEBUG))
					logger.debug("Initializating connection pool : " + poolName);
				pools.put(poolName, pool);
			} catch (Exception e) {
				logger.error("Can't connect throug JDBC driver: Name " + poolName, e);
			}
		}
	}

	public void logLeasedConnections() {
		Enumeration poolKeys = pools.keys();
		while (poolKeys.hasMoreElements()) {
			String poolKey = (String) poolKeys.nextElement();
			logger.warn("POOL: " + poolKey);
			logger.warn("-----------------------------------");
			((DBConnectionPool) pools.get(poolKey)).logLeasedConnections();
		}
	}

	/**
	 * Loads and registers all JDBC drivers. This is done by the
	 * DBConnectionManager, as opposed to the DBConnectionPool, since many pools
	 * may share the same driver.
	 *  
	 */
	private void loadDrivers() {

		String driverClassName = null;
		for (int i = 0; (driverClassName = SystemConfigUtil.getInstance().getStringProperty(
				PREFACE + "." + "CONNECTION[" + i + "].DRIVER", null)) != null; i++) {
			try {
				Driver driver = (Driver) Class.forName(driverClassName).newInstance();
				DriverManager.registerDriver(driver);
				drivers.addElement(driver);
				if (logger.isEnabledFor(Level.DEBUG))
					logger.debug("Registered JDBC driver " + driverClassName);
			} catch (Exception e) {
				logger.error("Can't register JDBC driver: " + driverClassName, e);
			}
		}
	}

}
